useImperativeHandle
🧑🏻💻 useImperativeHandle
useImperativeHandle은 ref로 노출되는 핸들을 사용자가 직접 정의할 수 있게 해주는 React 훅이다.
( imperative : 명령적인, 피할 수 없는, 반드시 해야 하는, 필수의 )
✅ useImperativeHandle 문법
useImperativeHandle(ref, createHandle, dependencies?)
| ref | forwardRef 렌더 함수에서 두 번째 인자로 받은 ref |
|---|---|
| createHandle | 인자가 없고 노출하려는 ref 핸들을 반환하는 함수 |
dependencies는createHandle코드 내에서 참조하는 모든 반응형 값을 나열한 목록이다. 리렌더링으로 인해 일부 의존성이 변경되거나 이 인수를 생략한 경우,createHandle함수가 다시 실행되고 새로 생성된 핸들이 ref에 할당된다.useImperativeHandle은undefined를 반환한다. (return값 없음)
🧑🏻💻 알고 가기
✅ 주의 사항
- 컴포넌트의 최상위 레벨에서 호출 가능, 또한 커스텀 훅에서 호출 가능하다. (React 훅)
- prop으로 표현할 수 있는 것은 ref를 사용하지 않는다.
- 예를 들어,
Modal컴포넌트에서{open, close}와 같은 명령형 핸들로 노출하는 대신<Modal isOpen={isOpen} />과 같은isOpenprop을 권장한다. 이로써 코드가 더 간결하고 예측 가능하며, 컴포넌트의 독립성과 재사용성이 향상된다. - props로 표현할 수 없는 것에 대한 예로는 특정 노드로 스크롤하기, 노드에 초점 맞추기, 애니메이션 촉발하기, 텍스트 선택하기 등이 있다.
- 예를 들어,
🧑🏻💻 활용 및 생각할 거리
✅ useImperativeHandle와 forwardRef
forwardRef와 함께 사용할 때부모 컴포넌트에서 자식 컴포넌트의 레퍼런스를 획득하고
useImperativeHandle을 사용하여 레퍼런스에 노출할 메서드나 데이터를 정의한다. 이는 부모 컴포넌트에서 자식 컴포넌트를 직접 조작해야 하는 경우에 유용하다.forwardRef없이 사용할 때컴포넌트 내부에서 특정 명령형 동작을 외부로 노출해야 하는 경우에
forwardRef없이 가능하다.- 코드로 살펴보기
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});forwarRef만 사용하면 부모 컴포넌트는 전체
<input>DOM 노드를 ref로 전달받게 된다.만약
<input>DOM 노드 전체를 ref로 전달하는 대신 DOM 노드에 대한 사용자 지정 값만 부모에게 전달하고 싶다면, 위의 코드와 같이useImperativeHandle를 사용하여 한 메서드만을 전달할 수 있다.import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}이제 부모 컴포넌트가
MyInput에 대한 ref를 가져오면focus메서드를 호출할 수 있다. 그러나 기본<input>DOM 노드의 전체 액세스 권한은 없다.
✅ useImperativeHandle를 왜 사용할까요?
객체 지향 프로그래밍에서는 캡슐화랑 은닉화가 중요하다. 비슷한 역할을 하는 속성(데이터)과 메소드(행동)들을 하나의 틀에 담는 것이다
- ex) 캡슐화 : 라면 끓이기 예시
- 가스레인지 : 가열의 역할
- 물 넣기 : 라면의 물을 맞추는 역할
- 재료 넣기 : 라면의 재료를 넣는 역할
이런 캡슐화는 기본적으로는 바람직하지만 button이나 input 요소 같이 재 사용성이 높은 말단 요소(가장 끝에서 작동하는 요소)에서는 불편할 수 있다.
이런 경우 DOM 요소에 접근하기 위해 사용되었던 ref 같은 경우 캡슐화로 인한 불편한 예시가 될 수 있는데, 이럴 경우 우리는 forwardRef를 사용해서 props로 ref를 내려줄 수 있다. 그러나 이런 경우 ref를 통한 DOM 조작의 전 권을 다 주게 된다.
필요한 기능 외로 건드리면 문제가 발생할 수도 있다…😢
그렇기 때문에 이럴 경우 useImperativeHadle을 사용한다. 원하는 DOM 조작 요소, 예를 들면 focus(), scroll() 같은 조작 요소들만 사용할 수 있게 허락할 수 있다!
✅ useImperativeHandle 사용 예시 코드
// App.js 부모 컴포넌트
import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
// This won't work because the DOM node isn't exposed:
// 이 작업은 DOM 노드가 노출되지 않으므로 작동하지 않습니다.
// ref.current.style.opacity = 0.5;
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
// MyInput.js 자식 컴포넌트
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
export default MyInput;
위에 코드와 같이 forwardRef로 전달 받은 ref 를 인수로 받으며 사용할 DOM 요소들만 정의해 준다. 부모 컴포넌트에서는 자식 컴포넌트에서 useImperativeHandle로 정의해 준 DOM 요소만 사용 가능하다.